package cn.virens.web.components.shiro.oauth2.filter;

import java.io.IOException;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import cn.hutool.core.util.StrUtil;
import cn.virens.oauth2.Oauth2Client;
import cn.virens.oauth2.standard.Oauth2AuthorizeBuilder;
import cn.virens.util.Assert;
import cn.virens.web.components.shiro.ShiroAuthInterface;
import cn.virens.web.components.shiro.oauth2.Oauth2AuthenticationToken;

public class Oauth2AuthorizingFilter extends AuthenticatingFilter {

	private Boolean useSsl = false;

	private String codeParam = "code";
	private String stateParam = "state";
	private String errorParam = "error"; // 错误码-参数名
	private String errorDescParam = "error_description";// 错误详情-参数名

	private String failureUrl = "/error";

	private Oauth2Client oauth2Client;

	@Autowired
	@Qualifier(ShiroAuthInterface.NAME)
	private ShiroAuthInterface shiroAuthInterface;

	public Boolean getUseSsl() {
		return useSsl;
	}

	public void setUseSsl(Boolean useSsl) {
		this.useSsl = useSsl;
	}

	public String getCodeParam() {
		return codeParam;
	}

	public void setCodeParam(String codeParam) {
		this.codeParam = codeParam;
	}

	public String getStateParam() {
		return stateParam;
	}

	public void setStateParam(String stateParam) {
		this.stateParam = stateParam;
	}

	public String getErrorParam() {
		return errorParam;
	}

	public void setErrorParam(String errorParam) {
		this.errorParam = errorParam;
	}

	public String getErrorDescParam() {
		return errorDescParam;
	}

	public void setErrorDescParam(String errorDescParam) {
		this.errorDescParam = errorDescParam;
	}

	public String getFailureUrl() {
		return failureUrl;
	}

	public void setFailureUrl(String failureUrl) {
		this.failureUrl = failureUrl;
	}

	public Oauth2Client getOauth2Client() {
		return oauth2Client;
	}

	public void setOauth2Client(Oauth2Client oauth2Client) {
		this.oauth2Client = oauth2Client;
	}

	@Override
	protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
		Oauth2AuthenticationToken authenticationToken = new Oauth2AuthenticationToken();
		authenticationToken.setState(WebUtils.getCleanParam(request, stateParam));
		authenticationToken.setCode(WebUtils.getCleanParam(request, codeParam));
		authenticationToken.setRedirectUri(getLoginUrl());

		return authenticationToken;
	}

	@Override
	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
		// 判断是否错误请求
		if (isFailureRequest(request, response)) {
			redirectToFailure(request, response);

			return false;
		}

		// 判断是否登录
		Subject subject = getSubject(request, response);
		if (!subject.isAuthenticated() && !isLoginRequest(request, response)) {
			redirectToLogin(request, response);

			return false;
		}

		// 执行登陆操作
		return executeLogin(request, response);
	}

	/**
	 * 重定向到登录失败页
	 * 
	 * @param  request
	 * @param  response
	 * @param  error
	 * @param  errorDesc
	 * @throws IOException
	 */
	protected void redirectToFailure(ServletRequest request, ServletResponse response) throws IOException {
		String error = WebUtils.getCleanParam(request, errorParam);
		String errorDesc = WebUtils.getCleanParam(request, errorDescParam);
		String decodeError = WebUtils.decodeRequestString(WebUtils.toHttp(request), error);
		String decodeErrorDesc = WebUtils.decodeRequestString(WebUtils.toHttp(request), errorDesc);

		WebUtils.issueRedirect(request, response, failureUrl + "?error=" + decodeError + "error_desc=" + decodeErrorDesc);
	}

	/**
	 * 重定向到登录页
	 */
	@Override
	protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
		Assert.isNull(oauth2Client, "Oauth2RequestClient is null");

		// 构建Oauth2认证地址
		Oauth2AuthorizeBuilder authorizeBuilder = oauth2Client.authorize();
		authorizeBuilder.setRedirectUri(getLoginUrl(WebUtils.toHttp(request)));
		authorizeBuilder.setScope("snsapi_userinfo");

		// 重定向到登录地址
		WebUtils.issueRedirect(request, response, authorizeBuilder.build());
	}

	protected boolean isFailureRequest(ServletRequest request, ServletResponse response) {
		return StrUtil.isNotEmpty(WebUtils.getCleanParam(request, errorParam));
	}

	@Override
	protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
		return StrUtil.isNotEmpty(WebUtils.getCleanParam(request, codeParam));
	}

	@Override
	protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
		this.shiroAuthInterface.onLoginSuccess(String.valueOf(subject.getPrincipal()), getHost(request));

		WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());

		return false;
	}

	@Override
	protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
		this.shiroAuthInterface.onLoginFailure(String.valueOf(token.getPrincipal()), getHost(request));

		return super.onLoginFailure(token, e, request, response);
	}

	private String getLoginUrl(HttpServletRequest http) {
		String url = getLoginUrl();

		if (!StrUtil.startWith(url, "http")) {
			String host = http.getHeader("Host");

			if (Boolean.TRUE.equals(useSsl)) {
				return "https://" + host + url(url);
			} else {
				return "http://" + host + url(url);
			}
		} else {
			return url;
		}
	}

	private String url(String url) {
		return StrUtil.startWith(url, '/') ? url : ("/" + url);
	}
}
